home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Skunkware 98
/
Skunkware 98.iso
/
osr5
/
sco
/
scripts
/
admin
/
adduser
next >
Encoding:
Amiga
Atari
Commodore
DOS
FM Towns/JPY
Macintosh
Macintosh JP
NeXTSTEP
RISC OS/Acorn
Shift JIS
UTF-8
Wrap
Korn shell script
|
1997-08-26
|
37.0 KB
|
1,245 lines
#!/bin/ksh
# adduser: script to add a user to the system.
# @(#) adduser.ksh 3.0 97/06/27
# 91/01/25 john h. dubois iii (john@armory.com)
# 91/02/02 added interactive capability
# 91/09/27 put phone num in GCOS field if given, cleaned up
# 91/10/27 Changed format of address record written.
# Also, now writes address record to first word of $ADDRESS
# 92/03/07 changed mkuser.init invokation from execution to sh sourcing
# since the files are not executable under UNIX.
# Changed to use vars from /etc/default/authsh if no
# /etc/default/mkuser
# 92/03/20 Added code to use /etc/shadow if it exists
# Run authck -p if available
# 92/04/03 Use MIN_SUGGEST_UID, MIN_SUGGEST_GID, and LOGIN_GROUP throughout
# script so that they can be changed in mkuser.defs file
# 92/05/03 Move automatic uid generation after reading of mkuser.defs
# 93/05/18 Let $ADDRESS be separated by colons in addition to whitespace
# 93/07/09 Print correct warning if username is a mail alias.
# 93/11/14 Print warning if password is truncated by makepass.
# 93/12/16 Modified to use /tcb/bin/ap instead of doing everything dangerously
# 93/12/31 Moved all vars specific to an account into associative arrays.
# Added ability to process multiple account requests.
# 94/01/02 Check whether multiple values are given for an account parameter.
# 94/01/06 Print warnings at end for failed account creations & truncated
# passwords.
# 94/01/09 Moved group adding into ap processing. Moved ap and mkuser.init
# invokation to end of script. This allows all accounts to be
# created by a single instance of ap.
# 94/04/16 Exit if ap fails.
# 94/05/22 Use default address file if $ADDRESS not set.
# 94/09/12 Added -n option
# 94/12/10 Deal with phone numbers much better.
# Discard + chars before writing record to address database.
# Capitalize 1st char of names if they were entered all lower case.
# Require that ap_tmp file have nonzero size before running ap etc.
# 94/12/16 Phone num canonicalization bugfix
# 95/07/08 Fix to tr bug in above fix
# 95/07/28 Tell where ap file left if it fails.
# 95/08/24 Get rid of extra spaces in names
# 95/10/20 Use namespaces instead of associative arrays to speed things up
# 95/12/23 Use egrep, not /bin/egrep; new OS doesn't have a /bin/egrep
# 96/01/20 Make tempfile start with #; create it more safely
# 96/03/02 Set BadAccounts correctly.
# 96/03/17 Added x option.
# 96/03/20 Use sed instead of tr when ranges are needed because they have
# to be specified differently in different releases.
# 96/04/27 Remove tmpfile on error exit
# 96/05/10 Deal with all-lower-case names too.
# 97/06/27 Make filename given on command line work again.
#
# This script adds a user to a XENIX or UNIX system (with "traditional" or
# lower security) through the 'ap' utility.
# This script runs /usr/lib/mkuser/<shell>/mkuser.init to do the following:
# create user's home directory
# copy login files to user's home directory
# send the user welcome mail
# Bugs:
# Password is echoed when used interactively.
# If ap is not used, no attempt is made to lock passwd or group file
# (should use ale(ADM) to lock them)
# UIDs are reused, which is a security risk unless all old files, IPC objects,
# processes, etc. on the system that are owned by the uid are removed.
# uucp/mkuser.defs has MIN_SUGGEST_UID=100 in it, but it is ignored.
# @(#) mktempfile 1.0 96/05/22
# 96/01/20 jhdiii
# Usage: mkfiles name ...
# Creates the named files with some attempt at security.
# This will be more reliable if user do not have chown authority.
# Any file that contains no / characters is created in the user's tempdir.
# If TMP was not set, it is set by this function.
# Returns 0 on success; prints a diagnostic message & returns 1 on failure.
function mkfiles {
typeset file files
typeset -i i=0
: ${TMP:=$TMPDIR}
: ${TMP:=/tmp}
for file; do
[[ "$file" != */* ]] && file="$TMP/$file"
files[i]=$file
let i+=1
done
set -- "${files[@]}"
rm -f "$@" || {
# hopefully rm will have printed a more specific message.
print -u2 "Could not remove pre-existing files."
return 1
}
for file; do
# Use >> to avoid 0'ing the file in case a symlink was just made from
# the filename to something we don't want to truncate
>> "$file" || {
print -u2 "Could not create temp file $file"
return 1
}
# These are very suspicious
[ -s "$file" ] && {
print -u2 "New tempfile is not empty!!!"
return 1
}
[ -O "$file" ] || {
print -u2 "Do not own $file!!!"
return 1
}
done
# Could do some more stuff here, but anyone concerned with security should
# have chown authorization off for most users.
return 0
}
# Usage: mktempfile name
# Creates a tempfile name tempdir/#name$$, and sets the global mktempfile_ret
# to that name. tempdir is a temp directory in $TMP, $TMPDIR, or /tmp, and $$
# is the PID of the current process.
# name should be 8 characters or less, so that the resulting filename will
# not be more than 14 characters long (a limit on some machines).
# Returns 0 on success, prints a diagnostic message & returns 1 on failure.
function mktempfile {
typeset file="#$1$$"
mkfiles "$file"
mktempfile_ret="$TMP/#$1$$"
}
## Start of nvar lib
# separate-namespace routines
# 95/10/20 john h. dubois iii (john@armory.com)
# These routines store values in variables whose namespace is separated
# from other variables by preceding them with a prefix.
# prefixes must have names that are valid shell variable names.
# Usage: nStore <prefix> <varname> <value> <append>
# Stores value <value> in the <varname> that is part of namespace <prefix>
# A null <value> may be given.
# If a 4th argument is given, the value is appended to the current value
# stored for the variable (if any).
# Return value is 0 for success,
# 2 for failure due to bad varname, 3 for bad syntax
function nStore {
typeset var="$1_$2" Val=$3
[ "$var" = _ ] && return 2
case $# in
3) eval $var=\$Val;;
4) eval $var=\"\$$var\$Val\";;
*) return 3;;
esac
return 0
}
# Usage: nGet [-n] <prefix> <var> <result>
# Gets the value of var <var> in namespace <prefix> and stores it in shell
# variable <result>. Returns failure status if the value was null.
# If -n is given, <result> is not touched in the value is null.
# result may have the form array[n].
# If <result> is not given, nothing is assigned; only the return status is set.
function nGet {
typeset noTouch=false var result
if [ "$1" = -n ]; then
noTouch=true
shift
fi
var="$1_$2"
result=$3
eval r=\$$var
[[ $# -eq 3 && ( $noTouch = false || -n "$r" ) ]] && eval $result=\$r
[ -n "$r" ]
}
# Usage: nUnset <prefix> <var> ...
# Each variable <var> in namespace <prefix> is unset.
function nUnset {
typeset prefix=${1}_ var
shift
for var; do
eval unset $prefix$var
done
}
# Usage: nGetMany <array> <prefix> <varname> ...
# Each of the named variables in namespace <prefix> is stored in shell array
# <arry> with indices starting with 0.
# The number of variables that had non-null values is returned (it will
# wrap around if more than 255 values are stored).
function AGetMany {
typeset -i NumNonNull=0 ind=0
typeset Arr=$1 prefix=$2 var
shift 2
for var; do
nGet $prefix "$var" "$Arr[$ind]" && let NumNonNull+=1
let ind+=1
done
return $NumNonNull
}
# set_vars: store variable assignments in a namespace.
# 93/12/28 John H. DuBois III (john@armory.com)
# Usage: set_vars prefix [filename ...]
# where the lines in filename (or the input) are of the form
# var=value
# value may contain whitespace, backslashes, quote characters, etc.;
# they will become part of the value assigned.
# Lines that begin with a # (optionally preceded by whitespace),
# lines that do not contain a '=', and lines that contain illegal characters
# in the var part are ignored.
# The only legal characters are [a-zA-Z0-9_]
# Values are stored in variables of the name given before the =, preceded by
# prefix. prefix may be given as a null string.
# Example: 'set_vars foo_ bar' will read the file bar. If bar contains the line
# blit=xyz
# then this shell variable assignment will be made:
# foo_blit=xys
function set_vars {
typeset prefix digits
prefix=$1
# If prefix is non-null, first char of name may be a digit
[ -n "$prefix" ] && digits=0-9
shift
for file; do
if [ ! -f "$file" ]; then
print -u2 -- "$file: No such file."
return 1
fi
if [ ! -r "$file" ]; then
print -u2 -- "$file: Could not open for reading."
return 1
fi
done
# return exit status of eval
# Delete lines that start with # or which do not contain an =
# or which try to assign to bad var names
# Quote '
# Put new ' around value
eval "$(sed "
/^[ ]*#/d
/^[a-zA-Z_$digits][a-zA-Z0-9_]*=/!d
s/'/'\\\\''/g
s/=/='/
s/$/'/
s/^/$prefix/" "$@")"
}
# Usage: nset_vars <prefix> [filename ...]
# Any legal vars assigned to in the given files are stored in namespace <prefix>
function nset_vars {
typeset prefix=${1}_
shift
set_vars $prefix "$@"
}
# Usage: nChkStore <prefix> <varname> <value>
# Exit if <varname> is already set in namespace <prefix>, else set it to <value>
function ChkStore {
typeset prefix=$1 var=$2 value=$3
if nGet $prefix $var; then
print -u2 "Error: $index already set. Exiting."
exit 1
else
nStore $prefix $var "$value"
fi
}
## End of nvar lib
# Print help message.
# Globals used: Name, Usage, PAGER, MIN_SUGGEST_UID, LOGIN_GROUP
# Globals changed: none.
function help {
echo \
"$Name: add a user to the system.
$Usage
If a filename is given, it is read for the neccessary information. Otherwise,
if the input is a tty, the neccessary information is prompted for
interactively. If the input is not a tty, the input is read for the
information. In all cases except for interactive operation, the input read is
expected to consist of lines of the form
var=value
where value gives an appropriate value for var.
Each var should be one of the following (unrecognized vars are ignored):
acctname: Name of the account
password: Initial password
uid: Numeric user id
group: Login group (numeric or name)
name: Name/comment for the GCOS (comment) field of /etc/passwd
shell: Login shell
address: Address for (optional) address database
phone_num: Phone number for the GCOS field and (optionally) address database
acctname, name, and shell are required.
If multiple groups of lines that contain '=' characters are read,
separated by lines that do not contain '=' characters,
each group is processed as a separate account request.
A directory with the name of shell with the files required by mkuser
must exist in /usr/lib/mkuser.
If password is not given, the account is created without a password.
If a password of '*' is given, a literal '*' will be put in the user's
password field, so that no password will give access to the account.
If a normal password is given, it is encrypted with the \"makepass\" utility.
If \"makepass\" is not available, a null or '*' password should be given,
and the user's password set with \"passwd\".
The default for uid is the first unused uid that is MIN_SUGGEST_UID
(currently configured at $MIN_SUGGEST_UID) or higher.
The default for group is \"$LOGIN_GROUP\".
If $Name is being used non-interactively,
the default or given group must already exist in /etc/group.
The defaults for address and phone_num are null.
If an address was given for the user, then after the user has been created
an address record is written. The record is in the format
Name
Address
Phone number
Email address
+
If the environment variable ADDRESS is set, the address record is written to
the file given by the first word of its value. Otherwise, it is written to
the file $addrdef, if it exists. If not, it is written to the
standard output.
Normally, both informational and error messages are printed to the error output.
If stderr is not a tty, only error messages are printed.
'ap' is run to add a user to the tcb database.
Note: very little sanity checking of parameters is done.
Options:
-a: Do not write an address record.
-t: Test: the normal messages are printed, but the user is not actually added
to the system. Informational messages are printed even if stderr is not
a tty.
-x: Turn on debugging. Informational messages are printed even if stderr is
not a tty, and extra information is printed.
-r<num>: The number of login retries before the account is locked is set to
<num> for each account created.
-h: Print this help.
-n: Set non-interactive mode (expecting input of the form var=value) even if
input is a tty." | ${PAGER:-more}
}
# Test whether an integer variable is nonzero or zero
alias istrue="test 0 -ne"
alias isfalse="test 0 -eq"
# DoFlags: interpret command line args
# Usage: DoFlags "$@"
# Globals used: $*, Usage
# Globals set: test, debug, Interactive, Retries
# returns number of args that were options
function DoFlags {
typeset opt
typeset -i i
while getopts :ahntr:x opt; do
case $opt in
a)
DoAddr=0;;
t)
test=1
debug=1
;;
x)
set -x
debug=1
;;
n)
Interactive=0;;
h)
help
exit 0;;
r)
if i=$OPTARG && [ i -gt 0 ]; then
Retries="u_maxtries#$i:"
else
print -u2 "$Name: Bad value given for max retries."
exit 1
fi;;
+?)
print -u2 "$Name: options should not be preceded by a '+'."
exit 1
;;
:)
print -r -u2 -- \
"$Name: Option '$OPTARG' requires a value. Use -h for help."
exit 1
;;
?)
print -u2 "$Name: $OPTARG: bad option. Use -h for help."
exit 1
;;
esac
done
# remove args that were options
let OPTIND=OPTIND-1
return $OPTIND
}
# GetShells: sets Shells to be a list of available shells
# Globals set: Shells
function GetShells {
typeset s
cd /usr/lib/mkuser || {
print -u2 "Could not change dir to /usr/lib/mkuser. Exiting."
exit 1
}
for s in */mkuser.init; do
Shells="$Shells ${s%/mkuser.init}"
done
}
# GetGroups: set Groups to be a list of groups
# Globals set: Groups
function GetGroups {
set -- $(wc -l /etc/group)
if [ $1 -gt 22 ]; then
# If more than 22 groups, print only group name & remove newlines
set -- $(sed 's/:.*//' /etc/group)
Groups=$*
else
Groups=$(sed 's/:[^:]*:/ /;s/:/ /;s/,/ /g' /etc/group)
fi
}
# Globals set:
# vars in namespace Account
function GetAccountInfo {
typeset passwdquery vars var invar
GetShells # set Shells to list of available shells
GetGroups # set Groups to be a list of available groups
if whence makepass > /dev/null; then
passwdquery="Enter the new user's password (note: passwd will echo)"
else
passwdquery=\
"Press return for a null password, or enter '*' to prevent logins"
fi
vars="acctname name shell password uid group address phone_num"
set -- \
"Enter the new user's login name" \
"Enter the new user's name (the comment to put in /etc/passwd)" \
"Enter the new user's login shell. The available shells are:
$Shells" \
"$passwdquery" \
"Enter the new user's uid (or press return to use the next available uid)" \
"Enter the new user's login group, or press return to use the default group
($LOGIN_GROUP). The currently existing groups are:
$Groups" \
"Enter the new user's address" \
"Enter the new user's phone number"
for var in $vars; do
print -n -u2 "$1\n$var="
read invar
nStore Account $var "$invar"
until Test_$var; do
print -n -u2 "$1\n$var="
read invar
nStore Account $var "$invar"
done
shift
done
}
# Prints next unused uid
# Usage: SetUID min-uid [skip-uids]
function SetUID {
typeset UIDFound minuid=$1
shift
awk -F: -v uid=$minuid -v UIDsUsed="$*" '
{
uids[$3]
}
END {
split(UIDsUsed,Elem," +")
for (i in Elem)
uids[Elem[i]]
while (uid in uids)
uid++
print uid
}' /etc/passwd | read UIDFound
print $UIDFound
}
# Check account parameters
# In Account[], sets groupname and gid; may unset name; may rewrite phone_num,
# may remove path component from shell; sets encpass to encoded password
# Error message is printed & 1 is returned if certain tests fail.
# For non-interactive account creation.
function CheckParams {
istrue debug && set -x
Test_acctname || return 1
Test_password || return 1
nStore Account encpass "$encpass"
Test_uid || return 1
Test_group || return 1
Test_name || return 1
Test_shell || return 1
Test_phone_num || print -u2 "Phone number not set."
return 0
}
# Checks whether account already exists in /etc/passwd or is a mail alias
# Returns 1 of so; otherwise 0
# Prints err messages if name exists
function Test_acctname {
istrue debug && set -x
typeset accountname
nGet Account acctname accountname
if egrep "^$accountname:" /etc/passwd >| /dev/null; then
print -u2 -r "Account $accountname already exists."
return 1
fi
(/usr/mmdf/bin/malias $accountname | egrep 'no aliases') \
> /dev/null 2>&1 ||
print -u2 "Warning: $accountname is a mail alias. Creating account anyway."
return 0
}
# Checks whether password is a valid password
# Returns 0 if so; otherwise nonzero
# Side effects: sets "encpass" to encoded password
# (or * or nothing, if that's what password is)
# Prints err message if bad password
# Global vars: Account*, encpass
function Test_password {
typeset password
typeset accountname
nGet Account acctname accountname
nGet Account password password
MakePass "$password" "$accountname"
}
# Checks whether $uid already exists in /etc/passwd
# Returns 0 if it doesn't or is null, 1 if it does
# Prints err message if uid is already used
# Global vars: Account*
function Test_uid {
typeset uid
nGet Account uid uid
[ -z "$uid" ] && return 0
if [[ "$uid" != [0-9]* ]]; then
print -u2 -r "Bad uid $uid."
return 1
fi
if egrep "^[^:]*:[^:]*:$uid:" /etc/passwd >| /dev/null; then
print -u2 -r "uid $uid already assigned."
return 1
fi
return 0
}
# Usage: Test_group
# Checks whether groupname/gid <group> exists in /etc/group
# If non-interactive, returns 1 if it doesn't, 0 if it does
# If interactive and group does not exist, gives user the option
# of creating the group. If the option is rejected or group
# creation fails, returns 1; otherwise returns 0.
# Side effects:
# If found, sets group to group name and gid to group id.
# If interactive, may create a group by adding a line to /etc/group
# If non-interactive, prints an error message if group does not exist
# Global vars: $LOGIN_GROUP (default group id/name), Account*
function Test_group {
typeset group groupname gid
nGet Account group group
if [ -z "$group" ]; then
if [ -z "$LOGIN_GROUP" ]; then
print -u2 "\007\007\007ERROR in Test_group: LOGIN_GROUP not set."
exit 1
fi
group=$LOGIN_GROUP
fi
GoodGroup "$group" | read groupname gid
[ -z "$groupname" ] && return 1
nStore Account groupname "$groupname"
nStore Account gid "$gid"
return 0
}
# Usage: GoodGroup <group>
# Checks whether groupname/gid <group> exists in /etc/group
# If non-interactive, returns 1 if it doesn't, 0 if it does
# If interactive and group does not exist, gives user the option
# of creating the group. If the option is rejected or group
# creation fails, returns 1; otherwise returns 0.
# Side effects:
# If found, prints groupname & gid
# If interactive, may create a group by adding a line to /etc/group
# If non-interactive, prints an error message if group does not exist
function GoodGroup {
typeset group=$1
[ -z "$group" ] && return 1
if FindGroup "$group"; then :; else
print -u2 "Group $group does not exist."
if ((Interactive)); then
MakeGroup $group || return 1
else
return 1
fi
fi
print "$groupname" "$gid"
return 0
}
# Tests whether $name is a valid name that can be put in the GCOS field
# Returns 0 if it is, 1 if not
# Global vars: none
function Test_name {
typeset name newName=
typeset -u -L1 u # 1 char, upper case
typeset -l Comp # lower case
set -o noglob # make sure globbing is off; local to this func
nGet Account name name
if [[ "$name" = *:* ]]; then
print -u2 -r "Name/comment \"$name\" contains a ':'."
nStore Account name ""
return 1
fi
# Get rid of extra spaces
set -- $name
name="$*"
# If name was entered all in lower or upper case, convert the first char of
# each component to upper case & the rest to lower case
if [[ "$name" != *[A-Z]* ]]; then
for Comp in $name; do
u=$Comp
newName="$newName $u${Comp#?}"
done
nStore Account name "${newName#?}"
fi
return 0
}
# Tests whether phone_num is a valid phone number that can be put in
# the GCOS field
# Returns 0 if it is, 1 if it isn't
# Side effects:
# Rewrites NA phone numbers in format suitable for GCOS field:
# aaa/pppnnnn or ppp-nnnn
# No - between prefix and number if area code is given, because SCO finger
# daemon limits "office" field (where the phone # is put) to 11 chars.
# Rewrites non-NA phone numbers to exclude invalid chars.
# If phone_num isn't valid, sets it to null string and prints an error message
# Global vars: Account*
# 94/12/10 jhdiii Rewrote.
function Test_phone_num {
typeset phone_num Digits
typeset -i NumDig
nGet Account phone_num phone_num
[ -z "$phone_num" ] && return 0
# Use sed instead of tr because tr behaviour re needing
# [] around ranges is incompatible between releases.
Digits=$(print -r -- "$phone_num" | sed 's/[^0-9]//g')
NumDig=${#Digits}
# This test for whether a phone number is a NA number is based on 600
# phone numbers entered with account requests. It will fail for numbers
# given with phone extensions, descriptions, numbers given with some digits
# as alpha characters, more than one phone number given, etc.
# optional-whitespace
# optional area code
# optional leading 1
# optional hyphen after 1
# optional open-paren
# 3 digits
# optional close-paren
# any number of nondigits
# 3 digit prefix
# any number of nondigits
# 4 digit number
# optional whitespace
if print -r -- "$phone_num" | egrep '^ *((1-?)?\(?[0-9][0-9][0-9]\)?[^0-9]*)?[0-9][0-9][0-9][^0-9]*[0-9][0-9][0-9][0-9] *$' > /dev/null; then
# NA number
case $NumDig in
7) NewNum="${Digits%????}-${Digits#???}";;
10) NewNum="${Digits%???????}/${Digits#???}";;
11) NewNum="${Digits%???????}/${Digits#???}"
NewNum=${NewNum#1}
;;
*)
print -u2 "Phone number test failed?!"
;;
esac
elif [ NumDig -lt 10 ]; then
print -u2 "Invalid phone number: $phone_number"
NewNum=
else
# Get rid of any chars that should not exist anywhere in a phone #
NewNum=$(print -r -- "$phone_num" | sed 's/[^0-9/(),+. -]//g')
NewNum=${NewNum##*([!(+0-9])}
NewNum=${NewNum%%*([!0-9])}
[ ${#NewNum} -ne ${#phone_num} ] && print -u2 \
"Discarded invalid chars in phone number '$phone_num';
new number is: $NewNum"
fi
nStore Account phone_num "$NewNum"
[ -z "$NewNum" ]
}
# Tests whether shell is a valid shell
# Returns 0 if it is, 1 if it isn't
# Side effects: Removes any path component in shell
# If shell is not valid, prints error message
function Test_shell {
typeset shell
nGet Account shell shell
# ignore any attempt at full path name in shell
shell=${shell##*/}
nStore Account shell "$shell"
if [ ! -f /usr/lib/mkuser/$shell/mkuser.init ]; then
print -u2 -r "Don't know how to create a user with shell \"$shell\"."
return 1
fi
}
# Stub
function Test_address {
return 0
}
# FindGroup [gid|groupname]
# searches /etc/group for a group with either the name or gid given
# in the argument.
# If found, sets groupname to group name and gid to group id and returns
# status 0.
# If fails, returns nonzero.
# Globals used/modified: none.
function FindGroup {
typeset gname gpass rgid gusers
typeset IFS=:
unset gname
sed -n "/^$1:/p;/^[^:]*:[^:]*:$1:/p" /etc/group |
read gname gpass rgid gusers
if [ -n "$gname" ]; then
groupname=$gname
gid=$rgid
return 0
else
return 1
fi
}
# Makes a group. Returns nonzero on failure.
# Usage: MakeGroup gid|groupname
# Global vars:
# Sets gid and groupname.
# May create a new group in /etc/group.
# This function requires that the standard input & standard error streams
# be connected to an interactive session.
function MakeGroup {
typeset g=$1 badreply=true groupmade
while [ $badreply ]; do
print -n -u2 "Create it, Select another group name or Abort? [c/s/a] "
read reply || return 1
case $reply in
a*) return 1;;
s*) print -u2 -n "Enter a group name or number: "
read g || return 1;;
c*) ;;
*) continue;
esac
unset badreply
done
groupmade=
while [ -z "$groupmade" ]; do
if [[ "$g" = *([0-9]) ]]; then
gid=$g
print -u2 -n "Enter a group name: "
read groupname || return 1
if egrep "^$groupname:" /etc/group >| /dev/null; then
print -u2 "Group name $groupname is already in use."
else
group=$groupname
if [ -n "$gid" ]; then
groupmade=true
else
g=$groupname
fi
fi
else
groupname=$g
print -u2 -n \
"Enter a group id #, or press <return> to select the next available gid: "
read gid || return 1
if [ -n "$gid" ]; then
if egrep "^[^:]*:[^:]*:$gid:" /etc/group >| /dev/null; then
print -u2 "gid $gid is already in use."
else
groupmade=true
fi
else
# set gid to next unused gid
gid=`awk -F: '
{
gids[$3]
}'"
END {
for (gid = $MIN_SUGGEST_GID; gid in gids; gid++)
print gid
}
" /etc/group`
print -u2 "Group assigned gid $gid."
groupmade=true
fi
fi
done
print -u2 "Adding the following line to /etc/group:\n$groupname::$gid:"
isfalse test && echo "$groupname::$gid:" >> /etc/group
return 0
}
# MakePass: set encpass to encoded version of $1
# If password is '*' or null, make it the password without encrypting
# Globals changed: encpass, Truncated
# Usage: MakePass encoded-password accountname
function MakePass {
if [[ "$1" = @(\\*|) ]]; then
encpass="$1"
else
encpass=`print -r "$1" | makepass` ||
{ print -u2 "Bad password."; return 1; }
# If password is more than 8 chars, encoded password should be
# more than 13 chars.
if [[ "$1" = ?????????* && "$encpass" != ??????????????* ]]; then
print -u2 \
"\007\007\007*** Warning! Password truncated to 8 characters."
Truncated="$Truncated $2"
fi
fi
return 0
}
# SetMkuserDef: set vars from an account defaults file
# Usage: SetMkuserDef prefix filename ...
# prefix_vars set:
# HOME HOMEMODE PROFMODE MAILMODE LOGIN_GROUP
# MIN_SUGGEST_UID MIN_SUGGEST_GID OTHER_GROUPS
# If -d is given, set defaults for values not given
# Returns 0 if file cannot be read
function SetMkuserDef {
typeset file prefix var
typeset -i DefOK=0 SetDef=0
typeset VarList="HOME HOMEMODE PROFMODE MAILMODE LOGIN_GROUP
MIN_SUGGEST_UID MIN_SUGGEST_GID OTHER_GROUPS SHELL HOME_MODE HOME_DIR"
typeset $VarList
if [ "$1" = -d ]; then
SetDef=1
shift
fi
prefix=$1
for file; do
if [ -r $file ]; then
nset_vars $prefix "$file"
DefOK=1
break
fi
done
if isfalse DefOK; then
print -u2 "Could not read: $*"
return 1
fi
# Set default & alternate values
for var in HOME_DIR HOME_MODE LOGIN_GROUP; do
nGet -n $prefix $var $var
done
# Set pairs of <var-name>,<alternate-value>
set -- HOME "$HOME_DIR" HOMEMODE "$HOME_MODE" OTHER_GROUPS "$LOGIN_GROUP"
istrue SetDef && set -- "$@" PROFMODE 600 MAILMODE 600 LOGIN_GROUP group \
MIN_SUGGEST_UID 200 MIN_SUGGEST_GID 50
while [ $# -gt 0 ]; do
# For each var, if it is not set and the alternate var is, set the
# var to the value of the alternate var.
nGet $prefix $1 || {
[ -n "$2" ] && nStore $prefix $1 "$2"
}
shift 2
done
return 0
}
# AddTCBap: Add an account profile record to a file to be processed by ap
# File passed to ap looks like this:
# blort:x:450:50:Todd:/u/blort:/bin/ksh
# blort:u_name=blort:u_id#450:\
# :u_pwd=x.5wL0kjhWqk.:\
# :u_type=general:u_lock@:chkent:
# Usage: AddTCBap acctname encpass uid gid GCOS home shell [group ...]
# Global data used:
# debug nonzero if debugging info should be printed
# ap_tmp file to write record to
# Retries: Max login retries field, if any. Should be in the form:
# u_maxtries#n:
function AddTCBap {
typeset aprec group groups= xgroupname xgid
typeset acctname=$1 encpass=$2 uid=$3 gid=$4 GCOS=$5 UserHome=$6
typeset FullShell=$7
if [ $# -lt 7 ]; then
print -u2 "\007\007\007ERROR in AddTCBap: only $# args passed:"
print -u2 "acctname=$1:encpass=$2:uid=$3:gid=$4:GCOS=$5:UserHome=$6"
return 2
fi
shift 7
if [ $# -gt 0 ]; then
for group; do
GoodGroup "$group" | read xgroupname xgid
if [ -z "$xgroupname" ]; then
print -u2 "Aborting account creation due to bad group."
return 1
fi
groups="$groups
$xgroupname::$xgid:"
done
groups="$groups
ENDOFGROUPS::0:"
fi
aprec="$acctname:x:$uid:$gid:$GCOS:$UserHome:$FullShell
$acctname:u_name=$acctname:u_id#$uid:u_pwd=$encpass:u_type=general:${Retries}u_lock@:chkent:$groups"
print -r "Record to be processed by /tcb/bin/ap:
$aprec"
if isfalse test; then
print -r "$aprec" >> $ap_tmp
fi
return 0
}
# AddTCB: Add user to password and possibly shadow files
# Run authck -p -s -y if neccessary
# Global data used:
# acctname user account name
# encpass encoded password
# uid numeric uid
# gid numeric login group id
# GCOS comment for GCOS field
# HOME home directory
# SHELL login shell
# test nonzero if no actual actions should be taken
# notcb nonzero if authck shouldn't be run.
#function AddTCB {
# typeset shadowline passwdline
#
# unset shadowline
# if [ -f /etc/shadow ]; then
# shadowline="$acctname:$encpass:::"
# encpass=x
# fi
#
# passwdline="$acctname:$encpass:$uid:$gid:$GCOS:$HOME:$SHELL"
#
# # Add user to /etc/passwd
# print "Adding user \"$acctname\" to /etc/passwd with the following line:"
# print -r "$passwdline"
# isfalse test && print -r "$passwdline" >> /etc/passwd
#
# # Add user to /etc/shadow
# if [ -n "$shadowline" ]; then
# print \
# "Adding user \"$acctname\" to /etc/shadow with the following line:"
# print -r "$shadowline"
# isfalse test && print -r "$shadowline" >> /etc/shadow
# fi
# isfalse test && isfalse notcb &&
# [ -x /tcb/bin/authck ] && /tcb/bin/authck -p -s -y
#}
# Usage: AddToGroups group ...
# Globals used: test
#function AddToGroups {
# typeset group IFS=,
#
# for group; do
# # Add user to /etc/group
# print "Adding user \"$acctname\" to group \"$group\" in /etc/group..."
# isfalse test && {
# Test_group
# cp /etc/group /etc/group-
# sed "/^$group:/s/\(.*\)/\1,$acctname/" /etc/group- > /etc/group
# }
# done
#}
# Add user to system
# Globals used: Account Mkuser* ADDRESS test uid AccountVars
# Globals changed: BadAccounts MILind MkuserInitLines UIDsUsed
# Script is aborted if mkuser files for selected shell cannot be read.
# 1 is returned if invalid values are given for account params.
function MakeUser {
istrue debug && set -x
typeset GCOS Entry OIFS UserHome var
typeset $AccountVars
typeset MkuserVars="HOME OTHER_GROUPS HOMEMODE MAILMODE PROFMODE SHELL"
typeset $MkuserVars ret
typeset -i i
unset uid
# Check/modify values in Account
if CheckParams; then :; else
nGet -n Account acctname acctname
BadAccounts="$BadAccounts $acctname"
return 1
fi
for var in $AccountVars; do
nGet -n Account $var $var
ret=$?
if [[ $var != @(phone_num|uid|address) ]] && eval [ -z \"\$$var\" ]
then
print -u2 "\007\007\007ERROR in MakeUser: null value for" \
"required parm $var; nGet returned $ret"
return 2
fi
done
for var in $MkuserVars; do
nGet -n Mkuser $var $var
done
# Set SHELL for given shell.
# If HOME is also given, it overrides home set for general accounts.
# Other variables may also be set here and override those
# set in /etc/default/mkuser etc.
# (MIN_SUGGEST_UID, LOGIN_GROUP, HOMEMODE, etc.)
# This must come after GetAccountInfo or nset_vars Account "@"
# so that $shell will be set.
nUnset ShellVars $MkuserVars
SetMkuserDef ShellVars /usr/lib/mkuser/$shell/mkuser.defs || return 2
for var in $MkuserVars; do
nGet -n ShellVars $var $var
done
# Must do this after mkuser.defs is read, since the shell may have
# a special min uid, etc.
if [ -z "$uid" ]; then
uid=$(SetUID $MIN_SUGGEST_UID $UIDsUsed)
UIDsUsed="$UIDsUsed $uid"
print -u2 "User assigned uid $uid."
fi
UserHome=$HOME/$acctname
[ -n "$phone_num" ] && GCOS="$name,$phone_num" || GCOS=$name
AddTCBap $acctname "$encpass" $uid $gid "$GCOS" $UserHome $SHELL \
$OTHER_GROUPS || return $?
# Make home directory for user & put shell files in it
# mkuser.init usage:
# sh mkuser.init user group home homemode mailmode profmode
# homemode, mailmode and profmode are the modes to chmod
# user's home dir, mail spool file, and login files to.
MkuserInitLines[MILind]=\
"$shell $acctname $groupname $UserHome $HOMEMODE $MAILMODE $PROFMODE"
let MILind+=1
# print address for address file
if [ -n "$address" ]; then
# Address database uses '+' for record separation, and unfortunately
# recognizes them anywhere. So, get rid of all + chars in record.
Entry=\
"$(print -r -- "$name\n$address\n$phone_num\n$acctname@`hostname`\n" |
tr -d +)+"
OIFS=$IFS
IFS="$IFS:"
set -- $ADDRESS
IFS=$OIFS
if istrue DoAddr && isfalse test; then
if [ -n "$1" ]; then
print "$Entry" >> $1
elif [ -f $addrdef ]; then
print "$Entry" >> $addrdef
else
print -u3 "
Address file entry:
$Entry
"
fi
fi
fi
return 0
}
function ProcReq {
istrue debug && set -x
typeset -i LNum=1
# Start from scratch for each user
nUnset Account $allAccountVars
while [ LNum -le NLines ]; do
print -r "${Lines[LNum]}"
let LNum+=1
done | nset_vars Account
# Use values in Account to build user
# Return MakeUser's return status
MakeUser
}
# Start of main program
# Namespace Account stores account parameters.
# Normal vars are: acctname, name, shell, password, uid, group, address,
# phone_num, gid, groupname, encpass
unset ENV
PATH=$PATH:/usr/bin:/bin:/usr/local/admin
addrdef=/usr/local/public/address
Name=${0##*/}
Usage="Usage: $Name [-anht] [-r<retries>] [request-file]"
typeset -i test=0 debug=0 numflags=0 Interactive MILind=0 DoAddr=1
AccountVars="uid acctname phone_num name gid groupname address shell encpass"
allAccountVars="$AccountVars group password"
# Read /etc/default/(mkuser or authsh) for default values:
# HOME HOMEMODE PROFMODE MAILMODE LOGIN_GROUP
# MIN_SUGGEST_GID MIN_SUGGEST_UID OTHER_GROUPS
# Do this before DoFlags so that if help is asked for,
# the default values of certain params can be included in it.
SetMkuserDef -d Mkuser /etc/default/mkuser /etc/default/authsh || exit 1
# Used by Help
nGet Mkuser MIN_SUGGEST_UID MIN_SUGGEST_UID
# Used by Help and Test_group
nGet Mkuser LOGIN_GROUP LOGIN_GROUP
# Set this before running DoFlags so that -n can override it.
[ ! -t 0 ]
Interactive=$?
DoFlags "$@" # interpret command line args
shift $?
istrue debug && set -x
if [ $# -gt 0 ]; then # filename given on command line?
Interactive=0
exec < "$1" || exit 1
fi
# Informational messages are printed to fd 1 (stdout).
# If error output is a tty or -t is given,
# make them go to fd 2 (stderr).
# Otherwise make them go to /dev/null.
# fd 3 is set to stdout so that address line can be written to it.
# fd 2 is not changed; error and interactive message go to it.
[ -t 2 -o debug -ne 0 ] && exec 3>&1 1>&2 || exec 3>&1 1>/dev/null
mktempfile adduser. || {
print -u2 "$Name: could not make temp file. Exiting."
exit 1
}
ap_tmp=$mktempfile_ret
trap 'rm -f $ap_tmp' EXIT
# Store values in Account*
if ((Interactive)); then
# Prompt for vars & sets them.
GetAccountInfo
# Use values in Account* to build user
MakeUser
[ $? = 2 ] && exit 1
else
# Non-interactive; set account vars by std input or given filename.
typeset -i NLines=0
# Even raw read removes leading & trailing spaces.
# Leading is ok, but not trailing (might be part of a password).
# So, add a : to the end of each line, then get rid of it.
# After each group of lines that contain an '=', pass record to ProcRec.
sed 's/$/:/' "$@" | while read -r line; do
if [[ "$line" = *=* ]]; then
let NLines+=1
Lines[NLines]=${line%:}
else
if [ NLines -gt 0 ]; then
ProcReq
[ $? = 2 ] && exit 1
NLines=0
fi
fi
done
if [ NLines -gt 0 ]; then
ProcReq
fi
fi
if isfalse test && [ -s "$ap_tmp" ]; then
/tcb/bin/ap -v -r -f $ap_tmp || {
print -u2 "\007\007\007ERROR: ap failed! Aborting."
print -u2 "ap file left in $ap_tmp"
trap - EXIT
exit 1
}
print -u2 "Creating home dirs & sending newuser mail..."
# Input is made /dev/null because some mkuser scripts ask questions
for line in "${MkuserInitLines[@]}"; do
set -- $line
shell=$1
shift
/bin/sh /usr/lib/mkuser/$shell/mkuser.init "$@"
done < /dev/null
fi
[ -n "$BadAccounts" ] && print -u2 "Account creation failed for:$BadAccounts"
[ -n "$Truncated" ] && print -u2 "Passwords were truncated for:$Truncated"
# The EXIT trap will remove the tmpfile